package com.moyun.common.excel.listener;

import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import com.alibaba.excel.exception.ExcelDataConvertException;
import com.alibaba.excel.metadata.Cell;
import com.alibaba.excel.metadata.data.ReadCellData;
import com.alibaba.excel.util.ConverterUtils;
import com.moyun.common.annotation.excel.ExcelPropertyCheck;
import lombok.Getter;
import lombok.Setter;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.*;
import java.util.regex.Pattern;

/**
 * <h3>moyun-base</h3>
 * <p>Excel解析事件监听器的基类</p>
 *
 * @author : ybx
 * @date : 2022-04-01 20:44:21
 **/
@Slf4j
@Getter
@Setter
public abstract class BaseExcelAnalysisEventListener<T> extends AnalysisEventListener<T> {
    /**
     * 自定义用于暂时存储data。可以通过实例获取该值
     */
    protected List<T> data = new ArrayList<>();
    /**
     * 保存错误数据 一个Cell(一个对象)  一个Row(则是Cell集合) 一个 sheet(则是row集合)
     */
    protected List<List<Object>> errorData = new ArrayList<>();
    /**
     * 校验信息
     */
    public String validMsg;
    /**
     * 生成动态表头
     */
    protected List<List<String>> head = new ArrayList<>();
    /**
     * 成功导入的数量
     */
    protected Long successCount = 0L;
    /**
     * 失败导入的数量
     */
    protected Long errorCount = 0L;
    /**
     * 失败反馈文件名
     */
    protected String fileName;
    /**
     * 每次读取的最大数据条数 实际使用中可以3000条,然后清理list,方便内存回收
     */
    private static final int MAX_BATCH_COUNT = 10;

    @SneakyThrows
    @Override
    public void invoke(T t, AnalysisContext context) {
        //数据存储到list，供批量处理，或后续自己业务逻辑处理。
        //数据校验,如 判断手机号、身份证号 在数据库中已经存在
        /**
         * T 每一行数据 对应为 数据映射的JAVA对象
         */
        if (isAdd(t, context) && dataValid(t)) {
            successCount++;
            data.add(t);
            if (data.size() > MAX_BATCH_COUNT) {
                // 保存数据
                saveData(data);
                // 清空list
                data.clear();
            }
        } else {
            errorCount++;
            saveErrorData(context);
        }

    }

    /**
     * 如果需要操作表头数据,便重写该方法
     **/
    @Override
    public void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext context) {
        List<String> list = new ArrayList<>(headMap.values());
        log.info("-------------->进入到 invokeHeadMap() 方法了,当前表头信息: {}", String.join(" | ", list));
        /**
         * 初始化表头 将每个单元格的数据以List<String>的形式存储 (在原来的Excel 表头添加了 错误列信息提示 列)
         * 在有错误数据时备用
         */
        if (head.size() == 0) {
            head.add(new ArrayList<>(Collections.singleton("错误列信息提示")));
            list.forEach(item -> head.add(new ArrayList<>(Collections.singleton(item))));
            return;
        }
        for (int i = 0; i < list.size(); i++) {
            String str = list.get(i);
            List<String> headI = this.head.get(i + 1);
            headI.add(str);
        }
    }

    /**
     * @return boolean
     * @Author yangboxuan
     * @Description 对数据进行校验
     * @Date 2022/9/19 11:17
     * @Param [object, context]
     **/
    private boolean isAdd(T object, AnalysisContext context) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
        boolean flag = true;
        Class<?> aClass = object.getClass();

        Field[] declaredFields = aClass.getDeclaredFields();
        for (Field declaredField : declaredFields) {
            if (declaredField.isAnnotationPresent(ExcelPropertyCheck.class)) {
                ExcelPropertyCheck annotation = declaredField.getAnnotation(ExcelPropertyCheck.class);
                boolean allowNull = annotation.isAllowNull();
                String regix = annotation.regix();
                String name = declaredField.getName(); // 获取属性的名字
                name = name.substring(0, 1).toUpperCase() + name.substring(1); // 将属性的首字符大写，方便构造get，set方法
                String type = declaredField.getGenericType().toString(); // 获取属性的类型
                if (type.equals("class java.lang.String")) { // 如果type是类类型，则前面包含"class "，后面跟类名
                    Method m = object.getClass().getMethod("get" + name);
                    String value = (String) m.invoke(object); // 调用getter方法获取属性值
                    if (value == null) {
                        if (!allowNull) {
                            log.error(annotation.errorMsg());
                            flag = false;
                        }
//                        m = object.getClass().getMethod("set"+name,String.class);
//                        m.invoke(object, "");
                    } else if (!"".equals(regix)) {
                        if (!Pattern.matches(regix, value)) {
                            log.error("第{}行,{}", context.readRowHolder().getRowIndex(), value + annotation.errorMsg());
                            flag = false;
                        }
                    }
                }
            }
        }
        return flag;
    }

    /**
     * 在转换异常 获取其他异常下会调用本接口。抛出异常则停止读取。如果这里不抛出异常则 继续读取下一行。
     */
    @Override
    public void onException(Exception exception, AnalysisContext context) {
        errorCount++;
        //log.error("解析失败，但是继续解析下一行:{}", exception.getMessage());
        // 如果是某一个单元格的转换异常 能获取到具体行号
        // 如果要获取头的信息 配合invokeHeadMap使用
        saveErrorData(context);
        if (exception instanceof ExcelDataConvertException) {
            ExcelDataConvertException excelDataConvertException = (ExcelDataConvertException) exception;
            log.error("第{}行，第{}列映射异常，数据为:{}，原因为{}", excelDataConvertException.getRowIndex(),
                    excelDataConvertException.getColumnIndex(), excelDataConvertException.getCause().getMessage(), excelDataConvertException.getMessage());
        }
    }

    /**
     * @return void
     * @Author yangboxuan
     * @Description 保存错误数据
     * @Date 2022/9/19 11:19
     * @Param [context]
     **/
    protected void saveErrorData(AnalysisContext context) {
        Map<Integer, Cell> cellMap = context.readRowHolder().getCellMap();
        LinkedHashMap<Integer, ReadCellData<?>> map = new LinkedHashMap<>();
        cellMap.forEach((k, v) -> {
            ReadCellData<?> cell = (ReadCellData<?>) cellMap.get(k);
            map.put(k, cell);
        });
        Map<Integer, String> row = ConverterUtils.convertToStringMap(map, context);
        /**
         * 行数据,所以用 List对象来接收 一个Cell 对应一个Object
         */
        List<Object> responseRow = new ArrayList<>(row.values());
        responseRow.add(0, validMsg);
        errorData.add(responseRow);
    }

    protected abstract boolean dataValid(T object);

    protected abstract void saveData(List<T> data);


}
